using System;
using System.Threading;
using System.Windows;
using System.Windows.Threading;
using System.Diagnostics;

namespace Intuipic
{
	public sealed class Timer : FrameworkElement
	{
		public static readonly DependencyProperty IsExecutingProperty = DependencyProperty.Register("IsExecuting",
			typeof(bool),
			typeof(Timer));

		public static readonly DependencyProperty TimeInCurrentPeriodProperty = DependencyProperty.Register("TimeInCurrentPeriod",
			typeof(TimeSpan),
			typeof(Timer),
			new FrameworkPropertyMetadata(RecalculateRatio));

		public static readonly DependencyProperty TimeInTotalPeriodProperty = DependencyProperty.Register("TimeInTotalPeriod",
			typeof(TimeSpan),
			typeof(Timer),
			new FrameworkPropertyMetadata(RecalculateRatio));

		public static readonly DependencyProperty RatioOfCurrentPeriodCompleteProperty = DependencyProperty.Register("RatioOfCurrentPeriodComplete",
			typeof(double),
			typeof(Timer),
			new FrameworkPropertyMetadata(null, new CoerceValueCallback(CoerceRatioOfCurrentPeriodComplete)));

		public static readonly RoutedEvent PeriodFinishedEvent = EventManager.RegisterRoutedEvent("PeriodFinished",
			RoutingStrategy.Bubble,
			typeof(RoutedEventHandler),
			typeof(Timer));

		public bool IsExecuting
		{
			get
			{
				return (bool) GetValue(IsExecutingProperty);
			}
			set
			{
				SetValue(IsExecutingProperty, value);
			}
		}

		public TimeSpan TimeInCurrentPeriod
		{
			get
			{
				return (TimeSpan) GetValue(TimeInCurrentPeriodProperty);
			}
			private set
			{
				SetValue(TimeInCurrentPeriodProperty, value);
			}
		}

		public TimeSpan TimeInTotalPeriod
		{
			get
			{
				return (TimeSpan) GetValue(TimeInTotalPeriodProperty);
			}
			set
			{
				SetValue(TimeInTotalPeriodProperty, value);
			}
		}

		public double RatioOfCurrentPeriodComplete
		{
			get
			{
				return (double) GetValue(RatioOfCurrentPeriodCompleteProperty);
			}
			private set
			{
				SetValue(RatioOfCurrentPeriodCompleteProperty, value);
			}
		}

		public event RoutedEventHandler PeriodFinished
		{
			add
			{
				AddHandler(PeriodFinishedEvent, value);
			}
			remove
			{
				RemoveHandler(PeriodFinishedEvent, value);
			}
		}

		public Timer()
		{
			Thread thread = new Thread(Execute);
			thread.Name = "Timer";
			thread.IsBackground = true;
			thread.Start();
		}

		public void Reset()
		{
			TimeInCurrentPeriod = TimeSpan.Zero;
		}

		private void Execute()
		{
			DateTime? lastExecuted = null;

			while (true)
			{
				if (GetIsExecuting())
				{
					if (!lastExecuted.HasValue)
					{
						lastExecuted = DateTime.UtcNow;
					}

					TimeSpan dif = DateTime.UtcNow - lastExecuted.Value;
					SetTimeInCurrentPeriod(GetTimeInCurrentPeriod() + dif);

					if (GetTimeInCurrentPeriod() >= GetTimeInTotalPeriod())
					{
						SetTimeInCurrentPeriod(TimeSpan.Zero);
						OnPeriodFinished();
					}

					lastExecuted = DateTime.UtcNow;
				}
				else if (lastExecuted.HasValue)
				{
					//pausing does not cause the timer to continue
					lastExecuted = DateTime.UtcNow;
				}

				Thread.Sleep(250);
			}
		}

		private delegate bool GetIsExecutingHandler();
		private bool GetIsExecuting()
		{
			if (!Dispatcher.CheckAccess())
			{
				object obj = Dispatcher.Invoke(DispatcherPriority.Send, new GetIsExecutingHandler(GetIsExecuting));

				if (obj == null)
				{
					return false;
				}

				return (bool) obj;
			}
			else
			{
				return IsExecuting;
			}
		}

		private delegate TimeSpan GetTimeInCurrentPeriodHandler();
		private TimeSpan GetTimeInCurrentPeriod()
		{
			if (!Dispatcher.CheckAccess())
			{
				object obj = Dispatcher.Invoke(DispatcherPriority.Send, new GetTimeInCurrentPeriodHandler(GetTimeInCurrentPeriod));

				if (obj == null)
				{
					return TimeSpan.Zero;
				}

				return (TimeSpan) obj;
			}
			else
			{
				return TimeInCurrentPeriod;
			}
		}

		private delegate void SetTimeInCurrentPeriodHandler(TimeSpan timeSpan);
		private void SetTimeInCurrentPeriod(TimeSpan timeInCurrentPeriod)
		{
			if (!Dispatcher.CheckAccess())
			{
				Dispatcher.Invoke(DispatcherPriority.Send, new SetTimeInCurrentPeriodHandler(SetTimeInCurrentPeriod), timeInCurrentPeriod);
			}
			else
			{
				TimeInCurrentPeriod = timeInCurrentPeriod;
			}
		}

		private delegate TimeSpan GetTimeInTotalPeriodHandler();
		private TimeSpan GetTimeInTotalPeriod()
		{
			if (!Dispatcher.CheckAccess())
			{
				object obj = Dispatcher.Invoke(DispatcherPriority.Send, new GetTimeInTotalPeriodHandler(GetTimeInTotalPeriod));

				if (obj == null)
				{
					return TimeSpan.Zero;
				}

				return (TimeSpan) obj;
			}
			else
			{
				return TimeInTotalPeriod;
			}
		}

		private delegate void OnPeriodFinishedHandler();
		private void OnPeriodFinished()
		{
			if (!CheckAccess())
			{
			    Dispatcher.Invoke(DispatcherPriority.Send, new OnPeriodFinishedHandler(OnPeriodFinished));
			}
			else
			{
				RaiseEvent(new RoutedEventArgs(PeriodFinishedEvent));
			}
		}

		private static void RecalculateRatio(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
		{
			dependencyObject.CoerceValue(RatioOfCurrentPeriodCompleteProperty);
		}

		private static object CoerceRatioOfCurrentPeriodComplete(DependencyObject dependencyObject, object baseValue)
		{
			Timer timer = dependencyObject as Timer;
			Debug.Assert(timer != null);
			Debug.Assert(timer.TimeInTotalPeriod > TimeSpan.Zero);
			return timer.TimeInCurrentPeriod.TotalMilliseconds / timer.TimeInTotalPeriod.TotalMilliseconds;
		}
	}
}
